package ua.kpi.ipsa.expert.api.algorithm;

import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import ua.kpi.ipsa.expert.api.db.BDBDAO;
import ua.kpi.ipsa.expert.api.db.CodePortionTuple;
import ua.kpi.ipsa.expert.api.db.CodeTuple;
import ua.kpi.ipsa.expert.api.db.DAO;
import ua.kpi.ipsa.expert.api.db.DataAccessException;
import ua.kpi.ipsa.expert.api.db.PlagiarismHypoTuple;
import ua.kpi.ipsa.expert.api.net.SourceFile;

import com.sleepycat.je.Environment;

public class CodePresentationManager {
	private final DAO dao;
	private final Environment env;
	private final Map<String, CodePresentationTask> presentations = new HashMap<String, CodePresentationTask>();
	private int lastCodePortionID;
	private volatile boolean finishing;

	public CodePresentationManager(DAO dao, Environment env) {
		this.dao = dao;
		this.env = env;
	}

	public synchronized void update() {
		if (finishing)
			throw new IllegalStateException("Closing");
		try {
			lastCodePortionID = dao.getLastCodePortionID();
			this.notifyAll();
		} catch (DataAccessException e) {
			e.printStackTrace();
		}
	}

	public synchronized void addAlgorithmPresentations(Algorithm algo) {
		if (finishing)
			throw new IllegalStateException("Closing");
		for (String p : algo.getCodePresentationClasses())
			addCodePresentation(p);
		update();
	}

	/**
	 * @param algorithm
	 * @return latest code portion id that can be checked by algorithm
	 */
	public synchronized int getAlgorithmReadiness(Algorithm algorithm) {
		if (finishing)
			throw new IllegalStateException("Closing");
		int ans = Integer.MAX_VALUE;
		for (String c : algorithm.getCodePresentationClasses())
			ans = Math.min(ans, getCodePresentationReadiness(c));
		return ans;
	}

	public void close() {
		try {
			synchronized (this) {
				if (finishing)
					return;
				finishing = true;
				this.notifyAll();
			}
			for (Entry<String, CodePresentationTask> p : presentations
					.entrySet())
				p.getValue().join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	private void addCodePresentation(String clazz) {
		if (presentations.containsKey(clazz))
			return;
		CodePresentationTask cpi = new CodePresentationTask(clazz);
		presentations.put(clazz, cpi);
		cpi.start();
	}

	private int getCodePresentationReadiness(String codePresentation) {
		return presentations.get(codePresentation).readiness;
	}

	private class CodePresentationTask extends Thread {
		final String clazz;
		volatile int readiness;

		CodePresentationTask(String clazz) {
			this.clazz = clazz;
			setDaemon(true);
		}

		@Override
		public void run() {
			try {
				CodePresentationStorage p = (CodePresentationStorage) Class
						.forName(clazz).newInstance();
				p.init(dao, env);
				p.removeAll();
				for (;;) {
					synchronized (CodePresentationManager.this) {
						while (!finishing && readiness >= lastCodePortionID)
							CodePresentationManager.this.wait();
						if (finishing)
							break;
					}
					int next = dao.getNextCodePortionID(readiness);
					if (next == 0)
						throw new DataAccessException();
					for (CodeTuple c : dao.getCodeByPortionKey(next)) {
						if (finishing)
							break;
						p.add(c);
					}
					readiness = next;
				}
				p.close();
			} catch (DataAccessException | InterruptedException
					| InstantiationException | IllegalAccessException
					| ClassNotFoundException e) {
				e.printStackTrace();
			}
		}
	}

	// public static void main(String[] args) throws Exception {
	// Environment env = BDBDAO.getEnvironment(new File("db"));
	// DAO dao = new BDBDAO(env);
	// CodePresentationManager p = new CodePresentationManager(dao, env);
	// p.addAlgorithmPresentations(new Algo());
	// p.addAlgorithmPresentations(new Algo());
	// int x = -1;
	// int y;
	// for (int i = 0; i < 1000; ++i) {
	// y = p.getAlgorithmReadiness(new Algo());
	// if (x != y)
	// System.out.println(y);
	// x = y;
	// }
	// //Thread.sleep(500);
	// y = p.getAlgorithmReadiness(new Algo());
	// if (x != y)
	// System.out.println(y);
	// x = y;
	// System.out.println("pause");
	// CodePortionTuple cp = new CodePortionTuple();
	// dao.setCodePortion(cp);
	// CodeTuple c = new CodeTuple(0);
	// c.fileName = new String[] { "file" };
	// c.codePortionTupleFK = cp.codePortionPK;
	// dao.setCode(c);
	// System.out.println("c = " + c.codePK);
	// Thread.sleep(500);
	// for (int i = 0; i < 1000; ++i) {
	// y = p.getAlgorithmReadiness(new Algo());
	// if (x != y)
	// System.out.println(y);
	// x = y;
	// }
	// p.update();
	// Thread.sleep(500);
	// for (int i = 0; i < 1000; ++i) {
	// y = p.getAlgorithmReadiness(new Algo());
	// if (x != y)
	// System.out.println(y);
	// x = y;
	// }
	// p.close();
	// dao.close();
	// env.close();
	// }
}

// class Algo implements Algorithm {
//
// @Override
// public List<PlagiarismHypoTuple> process(int codeToAnalizeId,
// CodeRanker ranker, ProgressMonitor monitor) {
// return null;
// }
//
// @Override
// public String getName() {
// return null;
// }
//
// @Override
// public String[] getCodePresentationClasses() {
// return new String[] { Presentation.class.getName() };
// }
// }
//
// class Presentation implements CodePresentationStorage {
//
// @Override
// public void init(DAO dao, Environment env) throws DataAccessException {
// }
//
// @Override
// public void close() throws DataAccessException {
// }
//
// @Override
// public void add(CodeTuple code) throws DataAccessException {
// }
//
// @Override
// public void removeAll() throws DataAccessException {
// }
// }
